#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
#include "base/simapi.h"

#include "config.h"
#ifdef WITH_TM
# include "tm/tmapi.h"
#endif

#define DEFAULT_LOCK 0
#define MAX_WORKERS 128
#define NUM_PRODUCTS 20
#define NUM_COMPONENTS 100
#define MAX_DIGIT 8
#define MAX_QUALITY (10^MAX_DIGIT)
#define MIN_QUALITY (MAX_QUALITY * 0.2) 

#define MAX_PRIVATE_BUFFER 10000
#define MAX_GLOBAL_BUFFER (MAX_PRIVATE_BUFFER * MAX_WORKERS)

#define BLOCK_SIZE 64
#define SEED 1
#define RANDOM_NUMBER (TM_random() % MAX_QUALITY)

#define MATRIX_A 0x9908b0df /* constant vector a */
#define N 624

#define N 624
#define M 397
#define MATRIX_A 0x9908b0df   /* constant vector a */
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
#define LOWER_MASK 0x7fffffff /* least significant r bits */

/* Tempering parameters */
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
#define TEMPERING_SHIFT_U(y)  (y >> 11)
#define TEMPERING_SHIFT_S(y)  (y << 7)
#define TEMPERING_SHIFT_T(y)  (y << 15)
#define TEMPERING_SHIFT_L(y)  (y >> 18)

struct mt19937p {
    unsigned long mt[N];
    int mti;
    unsigned long mag01[2];
};

/* initializing the array with a NONZERO seed */
void sgenrand (unsigned long seed, struct mt19937p* config)
{
    config->mti = N+1;
    config->mag01[0] = 0x0;
    config->mag01[1] = MATRIX_A;
    config->mt[0]= seed & 0xffffffff;
    for (config->mti=1; config->mti<N; config->mti++) {
        config->mt[config->mti] = (69069 * config->mt[config->mti-1]) & 0xffffffff;
    }
}

#define MAX_RAND 2100000000

/* double  generating reals */
unsigned long genrand (struct mt19937p* config)
{
    unsigned long y;

    if (config->mti >= N) { /* generate N words at one time */
        int kk;

        for (kk=0;kk<N-M;kk++) {
            y = (config->mt[kk]&UPPER_MASK)|(config->mt[kk+1]&LOWER_MASK);
            config->mt[kk] = config->mt[kk+M] ^ (y >> 1) ^ config->mag01[y & 0x1];
        }
        for (;kk<N-1;kk++) {
            y = (config->mt[kk]&UPPER_MASK)|(config->mt[kk+1]&LOWER_MASK);
            config->mt[kk] = config->mt[kk+(M-N)] ^ (y >> 1) ^ config->mag01[y & 0x1];
        }
        y = (config->mt[N-1]&UPPER_MASK)|(config->mt[0]&LOWER_MASK);
        config->mt[N-1] = config->mt[M-1] ^ (y >> 1) ^ config->mag01[y & 0x1];

        config->mti = 0;
    }

    y = config->mt[config->mti++];
    y ^= TEMPERING_SHIFT_U(y);
    y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
    y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
    y ^= TEMPERING_SHIFT_L(y);

    if (y < 0) {
        y = y * -1;
    }

    y = y/2;
    while (y > MAX_RAND) {
        y = y/2;
    }

    return y;  /* for integer generation */
}

struct mt19937p *rGen[1];        // used in parallelized part

void TM_srandom ()
{
    rGen[0] = (struct mt19937p *)malloc(sizeof(struct mt19937p));
    sgenrand(1, rGen[0]);
}

long TM_random ()
{
    return (long)genrand(rGen[0]);
}

static int tm_mode;
static int num_workers;
static int fat_lock;

struct privateLogBuffer
{
    int len;
    char slot[MAX_PRIVATE_BUFFER];
};

struct globalLogBuffer
{
    int len;
    char slot[MAX_GLOBAL_BUFFER];
};

int products[MAX_WORKERS + 1][NUM_PRODUCTS][NUM_COMPONENTS + 1];
struct privateLogBuffer stringBuffers[MAX_WORKERS];
struct privateLogBuffer privateBuffers[MAX_WORKERS];
struct globalLogBuffer globalBuffer;

__inline__ void checkProduct (int productId)
{
    int i;
    int id = Sim_GetMyId();
    for (i = 0; i < NUM_COMPONENTS;i++) {
        if (products[id][productId][i] < MIN_QUALITY) {
            products[id][productId][NUM_COMPONENTS] = 0;
            return;
        }
    }

    products[id][productId][NUM_COMPONENTS] = 1;
}

__inline__ void prepareString (int productId)
{
    int i,j;
    int idx = 0;
    int len = 0;
    int id = Sim_GetMyId();
    for (j = 0; j < NUM_COMPONENTS + 1;j++) {
        for (i = 0; i < MAX_DIGIT; i++) {
            if (products[id][productId][j] == 0) {
                break;
            }

            stringBuffers[id].slot[idx]  = (char)(products[id][productId][j] % 10 + 48); 
            products[id][productId][j] /= 10;
            len++;
            idx++;
        }

        stringBuffers[id].slot[idx] = (char)' ';
        len++;
        idx++;
    }

    stringBuffers[id].len = len;
}

__inline__ void writeString ()
{
    int i;
    int id = Sim_GetMyId();
    int len = stringBuffers[id].len;

    if (tm_mode) {
        privateBuffers[id].len = len;
        for (i = 0; i < len; i++) {
            privateBuffers[id].slot[i] = stringBuffers[id].slot[i];
        }
    }
    else {
        for (i = 0; i < len; i++) {
            globalBuffer.slot[globalBuffer.len + i] = stringBuffers[id].slot[i];
        }
        globalBuffer.len += len;
    }
}

__inline__ void memoryBarrier ()
{
    int i;
    int id = Sim_GetMyId();
    int len = stringBuffers[id].len;
    for (i = 0; i < len; i++) {
        ;
    }
}

void commit_callback ()
{
#ifdef WITH_TM    
    int i;
    int id = Sim_GetMyId();
    int len = privateBuffers[id].len;

    TM_BeginClosed(); {
        for (i = 0; i < len; i++) {
            globalBuffer.slot[globalBuffer.len + i] = privateBuffers[id].slot[i];
        }
        globalBuffer.len += len;
    } 
    TM_EndClosed();
#endif
}

pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;

__inline__ void user_printf (int productID)
{
    prepareString(productID);

    if (!tm_mode && !fat_lock) {
        pthread_mutex_lock(&print_lock);
    }

    writeString();

    if (!tm_mode && !fat_lock) {
        pthread_mutex_unlock(&print_lock);
    }

    memoryBarrier();
}

void* TM_WorkThread (void* arglist)
{
#ifdef WITH_TM
    int i;
    Sim_Print1("[workThread] cpu [%d] : TM_WorkThread is initiated\n", Sim_GetMyId());
    for (i=0; i < NUM_PRODUCTS; i++) {
        TM_BeginClosed(); {
            checkProduct(i);
        }
        TM_EndClosed();

        TM_BeginClosed(); {
            TM_RegisterCommitHandler((CALLBACK_Func)commit_callback);
            user_printf(i);
        }
        TM_EndClosed();
    }
#endif
    return NULL;
}


void* Lock_WorkThread (void* arglist)
{
    int i;
    Sim_Print1("[workThread] cpu [%d] : Lock_WorkThread is initiated\n", Sim_GetMyId());

    for (i=0; i < NUM_PRODUCTS; i++) {
        checkProduct(i);

        if (fat_lock) {
            pthread_mutex_lock(&print_lock);
        }

        user_printf(i);

        if (fat_lock) {
            pthread_mutex_unlock(&print_lock);
        }
    }

    return NULL;
}

void mainX (int argc, const char** const argv, const char **envp)
{
    int i,j,k;

    tm_mode = (Sim_GetMode() == MODE_TM);

    if (argc < 3) {
        num_workers = Sim_GetNumCpus();
    }
    else {
        num_workers = atoi(argv[2]);
    }
    if (argc < 2) {
        fat_lock= DEFAULT_LOCK;
    } 
    else {
        int exe_mode = atoi(argv[1]);
        switch (exe_mode)
        {
        case 0:
            assert(tm_mode);
            break;
        case 1:
            assert(!tm_mode);
            fat_lock = 1;
            break;
        case 2:
            assert(!tm_mode);
            fat_lock = 0;
            break;
        default:
            assert(FALSE);
        }
    }

    Sim_Print3("[mainX] tm_mode = %d, num_workers = %d, fat_lock = %d \n", tm_mode, num_workers, fat_lock);

    TM_srandom();

    for (i =0; i < num_workers; i++) {
        for (j=0;j < NUM_PRODUCTS; j++) {
            if (tm_mode) {
#ifdef WITH_TM
                TM_BeginClosed();
#else
                Sim_Print0("Somehow mode is MODE_TM but WITH_TM was not defined.");
                exit(1);
#endif
            }
            {
                for (k=0; k < NUM_COMPONENTS; k++) {
                    products[i][j][k] = RANDOM_NUMBER;
                }
            }
            if (tm_mode) {
#ifdef WITH_TM
                TM_EndClosed();
#endif
            }
        }
    }

    Sim_BeginStats();
    Sim_ParallelFork(tm_mode ? TM_WorkThread : Lock_WorkThread, NULL);
    Sim_EndStats();

}

